/**
 * Contains a range of UTF-8 character offsets and token references that
 * identify the region of the source from which the AST derived.
 */
public struct Location: Equatable, Sendable {
    /**
     * The character offset at which this Node begins.
     */
    public let start: Int

    /**
     * The character offset at which this Node ends.
     */
    public let end: Int

    /**
     * The Token at which this Node begins.
     */
    public let startToken: Token

    /**
     * The Token at which this Node ends.
     */
    public let endToken: Token

    /**
     * The Source document the AST represents.
     */
    public let source: Source
}

/**
 * Represents a range of characters represented by a lexical token
 * within a Source.
 */
public final class Token: @unchecked Sendable {
    /// Token is sendable because it is not mutated after being Lexed, which is controlled
    /// internally.
    public enum Kind: String, CustomStringConvertible, Sendable {
        case sof = "<SOF>"
        case eof = "<EOF>"
        case bang = "!"
        case dollar = "$"
        case amp = "&"
        case openingParenthesis = "("
        case closingParenthesis = ")"
        case spread = "..."
        case colon = ":"
        case equals = "="
        case at = "@"
        case openingBracket = "["
        case closingBracket = "]"
        case openingBrace = "{"
        case pipe = "|"
        case closingBrace = "}"
        case name = "Name"
        case int = "Int"
        case float = "Float"
        case string = "String"
        case blockstring = "BlockString"
        case comment = "Comment"

        public var description: String {
            return rawValue
        }
    }

    /**
     * The kind of Token.
     */
    public let kind: Kind

    /**
     * The character offset at which this Node begins.
     */
    public let start: Int
    /**
     * The character offset at which this Node ends.
     */
    public let end: Int
    /**
     * The 1-indexed line number on which this Token appears.
     */
    public let line: Int
    /**
     * The 1-indexed column number at which this Token begins.
     */
    public let column: Int
    /**
     * For non-punctuation tokens, represents the interpreted value of the token.
     */
    public let value: String?
    /**
     * Tokens exist as nodes in a double-linked-list amongst all tokens
     * including ignored tokens. <SOF> is always the first node and <EOF>
     * the last.
     */
    public let prev: Token?
    public internal(set) var next: Token?

    init(
        kind: Kind,
        start: Int,
        end: Int,
        line: Int,
        column: Int,
        value: String? = nil,
        prev: Token? = nil,
        next: Token? = nil
    ) {
        self.kind = kind
        self.start = start
        self.end = end
        self.line = line
        self.column = column
        self.value = value
        self.prev = prev
        self.next = next
    }
}

extension Token: Equatable {
    public static func == (lhs: Token, rhs: Token) -> Bool {
        return lhs.kind == rhs.kind &&
            lhs.start == rhs.start &&
            lhs.end == rhs.end &&
            lhs.line == rhs.line &&
            lhs.column == rhs.column &&
            lhs.value == rhs.value
    }
}

extension Token: CustomStringConvertible {
    public var description: String {
        var description = "Token(kind: \(kind)"

        if let value = value {
            description += ", value: \(value)"
        }

        description += ", line: \(line), column: \(column))"

        return description
    }
}

public enum NodeResult {
    case node(Node)
    case array([Node])

    public var isNode: Bool {
        if case .node = self {
            return true
        }
        return false
    }

    public var isArray: Bool {
        if case .array = self {
            return true
        }
        return false
    }

    func get(key: IndexPathElement) -> NodeResult? {
        switch self {
        case let .node(node):
            guard let key = key.keyValue else {
                return nil
            }
            return node.get(key: key)
        case let .array(array):
            guard let key = key.indexValue else {
                return nil
            }
            return .node(array[key])
        }
    }

    func set(value: NodeResult, key: IndexPathElement) -> Self? {
        switch self {
        case let .node(node):
            guard let key = key.keyValue else {
                return nil
            }
            return .node(node.set(value: value, key: key))
        case var .array(array):
            switch value {
            case let .node(value):
                guard let key = key.indexValue else {
                    return nil
                }
                array[key] = value
                return .array(array)
            case let .array(value):
                return .array(value)
            }
        }
    }
}

/**
 * The list of all possible AST node types.
 */
public protocol Node: Sendable {
    var kind: Kind { get }
    var loc: Location? { get }
    func get(key: String) -> NodeResult?
    func set(value: NodeResult?, key: String) -> Self
}

public extension Node {
    func get(key _: String) -> NodeResult? {
        return nil
    }

    func set(value _: NodeResult?, key _: String) -> Self {
        // This should be overridden by each type on which it should do something
        return self
    }
}

extension Name: Node {}
extension Document: Node {}
extension OperationDefinition: Node {}
extension VariableDefinition: Node {}
extension Variable: Node {}
extension SelectionSet: Node {}
extension Field: Node {}
extension Argument: Node {}
extension FragmentSpread: Node {}
extension InlineFragment: Node {}
extension FragmentDefinition: Node {}
extension IntValue: Node {}
extension FloatValue: Node {}
extension StringValue: Node {}
extension BooleanValue: Node {}
extension EnumValue: Node {}
extension ListValue: Node {}
extension ObjectValue: Node {}
extension ObjectField: Node {}
extension Directive: Node {}
extension NamedType: Node {}
extension ListType: Node {}
extension NonNullType: Node {}
extension SchemaDefinition: Node {}
extension OperationTypeDefinition: Node {}
extension ScalarTypeDefinition: Node {}
extension ObjectTypeDefinition: Node {}
extension FieldDefinition: Node {}
extension InputValueDefinition: Node {}
extension InterfaceTypeDefinition: Node {}
extension UnionTypeDefinition: Node {}
extension EnumTypeDefinition: Node {}
extension EnumValueDefinition: Node {}
extension InputObjectTypeDefinition: Node {}
extension TypeExtensionDefinition: Node {}
extension SchemaExtensionDefinition: Node {}
extension DirectiveDefinition: Node {}

public struct Name: Sendable {
    public let kind: Kind = .name
    public let loc: Location?
    public let value: String

    init(loc: Location? = nil, value: String) {
        self.loc = loc
        self.value = value
    }
}

extension Name: Equatable {
    public static func == (lhs: Name, rhs: Name) -> Bool {
        return lhs.value == rhs.value
    }
}

public struct Document {
    public let kind: Kind = .document
    public let loc: Location?
    public private(set) var definitions: [Definition]

    init(loc: Location? = nil, definitions: [Definition]) {
        self.loc = loc
        self.definitions = definitions
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "definitions":
            guard !definitions.isEmpty else {
                return nil
            }
            return .array(definitions)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "definitions":
            guard
                case let .array(values) = value,
                let definitions = values as? [Definition]
            else {
                break
            }
            new.definitions = definitions
        default:
            break
        }
        return new
    }
}

extension Document: Equatable {
    public static func == (lhs: Document, rhs: Document) -> Bool {
        guard lhs.definitions.count == rhs.definitions.count else {
            return false
        }

        for (l, r) in zip(lhs.definitions, rhs.definitions) {
            guard l == r else {
                return false
            }
        }

        return true
    }
}

public protocol Definition: Node {}
extension OperationDefinition: Definition {}
extension FragmentDefinition: Definition {}

public func == (lhs: Definition, rhs: Definition) -> Bool {
    switch lhs {
    case let l as OperationDefinition:
        if let r = rhs as? OperationDefinition {
            return l == r
        }
    case let l as FragmentDefinition:
        if let r = rhs as? FragmentDefinition {
            return l == r
        }
    case let l as TypeSystemDefinition:
        if let r = rhs as? TypeSystemDefinition {
            return l == r
        }
    default:
        return false
    }

    return false
}

public enum OperationType: String, CaseIterable, Sendable {
    case query
    case mutation
    case subscription
}

public final class OperationDefinition {
    public let kind: Kind = .operationDefinition
    public let loc: Location?
    public let operation: OperationType
    public let name: Name?
    public let variableDefinitions: [VariableDefinition]
    public let directives: [Directive]
    public let selectionSet: SelectionSet

    init(
        loc: Location? = nil,
        operation: OperationType,
        name: Name? = nil,
        variableDefinitions: [VariableDefinition] = [],
        directives: [Directive] = [],
        selectionSet: SelectionSet
    ) {
        self.loc = loc
        self.operation = operation
        self.name = name
        self.variableDefinitions = variableDefinitions
        self.directives = directives
        self.selectionSet = selectionSet
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return name.map { .node($0) }
        case "variableDefinitions":
            guard !variableDefinitions.isEmpty else {
                return nil
            }
            return .array(variableDefinitions)
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        case "selectionSet":
            return .node(selectionSet)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                return self
            }
            return Self(
                loc: loc,
                operation: operation,
                name: name,
                variableDefinitions: variableDefinitions,
                directives: directives,
                selectionSet: selectionSet
            )
        case "variableDefinitions":
            guard
                case let .array(values) = value,
                let variableDefinitions = values as? [VariableDefinition]
            else {
                return self
            }
            return Self(
                loc: loc,
                operation: operation,
                name: name,
                variableDefinitions: variableDefinitions,
                directives: directives,
                selectionSet: selectionSet
            )
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                return self
            }
            return Self(
                loc: loc,
                operation: operation,
                name: name,
                variableDefinitions: variableDefinitions,
                directives: directives,
                selectionSet: selectionSet
            )
        case "selectionSet":
            guard
                case let .node(value) = value,
                let selectionSet = value as? SelectionSet
            else {
                return self
            }
            return Self(
                loc: loc,
                operation: operation,
                name: name,
                variableDefinitions: variableDefinitions,
                directives: directives,
                selectionSet: selectionSet
            )
        default:
            return self
        }
    }
}

extension OperationDefinition: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    public static func == (lhs: OperationDefinition, rhs: OperationDefinition) -> Bool {
        return lhs.operation == rhs.operation &&
            lhs.name == rhs.name &&
            lhs.variableDefinitions == rhs.variableDefinitions &&
            lhs.directives == rhs.directives &&
            lhs.selectionSet == rhs.selectionSet
    }
}

public struct VariableDefinition {
    public let kind: Kind = .variableDefinition
    public let loc: Location?
    public private(set) var variable: Variable
    public private(set) var type: Type
    public private(set) var defaultValue: Value?
    public private(set) var directives: [Directive]

    init(
        loc: Location? = nil,
        variable: Variable,
        type: Type,
        defaultValue: Value? = nil,
        directives: [Directive] = []
    ) {
        self.loc = loc
        self.variable = variable
        self.type = type
        self.defaultValue = defaultValue
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "variable":
            return .node(variable)
        case "type":
            return .node(type)
        case "defaultValue":
            return defaultValue.map { .node($0) }
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "variable":
            guard
                case let .node(node) = value,
                let variable = node as? Variable
            else {
                break
            }
            new.variable = variable
        case "type":
            guard
                case let .node(node) = value,
                let type = node as? Type
            else {
                break
            }
            new.type = type
        case "defaultValue":
            guard
                case let .node(node) = value,
                let defaultValue = node as? Value?
            else {
                break
            }
            new.defaultValue = defaultValue
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                break
            }
            new.directives = directives
        default:
            break
        }
        return new
    }
}

extension VariableDefinition: Equatable {
    public static func == (lhs: VariableDefinition, rhs: VariableDefinition) -> Bool {
        guard lhs.variable == rhs.variable else {
            return false
        }

        guard lhs.type == rhs.type else {
            return false
        }

        if lhs.defaultValue == nil, rhs.defaultValue == nil {
            return true
        }

        guard let l = lhs.defaultValue, let r = rhs.defaultValue else {
            return false
        }

        return l == r
    }
}

public struct Variable {
    public let kind: Kind = .variable
    public let loc: Location?
    public private(set) var name: Name

    init(loc: Location? = nil, name: Name) {
        self.loc = loc
        self.name = name
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        default:
            break
        }
        return new
    }
}

extension Variable: Equatable {
    public static func == (lhs: Variable, rhs: Variable) -> Bool {
        return lhs.name == rhs.name
    }
}

public final class SelectionSet {
    public let kind: Kind = .selectionSet
    public let loc: Location?
    public let selections: [Selection]

    init(loc: Location? = nil, selections: [Selection]) {
        self.loc = loc
        self.selections = selections
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "selections":
            guard !selections.isEmpty else {
                return nil
            }
            return .array(selections)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        switch key {
        case "selections":
            guard
                case let .array(values) = value,
                let selections = values as? [Selection]
            else {
                return self
            }
            return Self(
                loc: loc,
                selections: selections
            )
        default:
            return self
        }
    }
}

extension SelectionSet: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    public static func == (lhs: SelectionSet, rhs: SelectionSet) -> Bool {
        guard lhs.selections.count == rhs.selections.count else {
            return false
        }

        for (l, r) in zip(lhs.selections, rhs.selections) {
            guard l == r else {
                return false
            }
        }

        return true
    }
}

public protocol Selection: Node {}
extension Field: Selection {}
extension FragmentSpread: Selection {}
extension InlineFragment: Selection {}

public func == (lhs: Selection, rhs: Selection) -> Bool {
    switch lhs {
    case let l as Field:
        if let r = rhs as? Field {
            return l == r
        }
    case let l as FragmentSpread:
        if let r = rhs as? FragmentSpread {
            return l == r
        }
    case let l as InlineFragment:
        if let r = rhs as? InlineFragment {
            return l == r
        }
    default:
        return false
    }

    return false
}

public struct Field {
    public let kind: Kind = .field
    public let loc: Location?
    public private(set) var alias: Name?
    public private(set) var name: Name
    public private(set) var arguments: [Argument]
    public private(set) var directives: [Directive]
    public private(set) var selectionSet: SelectionSet?

    init(
        loc: Location? = nil,
        alias: Name? = nil,
        name: Name,
        arguments: [Argument] = [],
        directives: [Directive] = [],
        selectionSet: SelectionSet? = nil
    ) {
        self.loc = loc
        self.alias = alias
        self.name = name
        self.arguments = arguments
        self.directives = directives
        self.selectionSet = selectionSet
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "alias":
            return alias.map { .node($0) }
        case "name":
            return .node(name)
        case "arguments":
            guard !arguments.isEmpty else {
                return nil
            }
            return .array(arguments)
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        case "selectionSet":
            return selectionSet.map { .node($0) }
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "alias":
            guard
                case let .node(node) = value,
                let alias = node as? Name
            else {
                break
            }
            new.alias = alias
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        case "arguments":
            guard
                case let .array(values) = value,
                let arguments = values as? [Argument]
            else {
                break
            }
            new.arguments = arguments
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                break
            }
            new.directives = directives
        case "selectionSet":
            guard
                case let .node(value) = value,
                let selectionSet = value as? SelectionSet
            else {
                break
            }
            new.selectionSet = selectionSet
        default:
            break
        }
        return new
    }
}

extension Field: Equatable {
    public static func == (lhs: Field, rhs: Field) -> Bool {
        return lhs.alias == rhs.alias &&
            lhs.name == rhs.name &&
            lhs.arguments == rhs.arguments &&
            lhs.directives == rhs.directives &&
            lhs.selectionSet == rhs.selectionSet
    }
}

public struct Argument {
    public let kind: Kind = .argument
    public let loc: Location?
    public private(set) var name: Name
    public private(set) var value: Value

    init(loc: Location? = nil, name: Name, value: Value) {
        self.loc = loc
        self.name = name
        self.value = value
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        case "value":
            return .node(value)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        case "value":
            guard
                case let .node(node) = value,
                let value = node as? Value
            else {
                break
            }
            new.value = value
        default:
            break
        }
        return new
    }
}

extension Argument: Equatable {
    public static func == (lhs: Argument, rhs: Argument) -> Bool {
        return lhs.name == rhs.name &&
            lhs.value == rhs.value
    }
}

public protocol Fragment: Selection {}
extension FragmentSpread: Fragment {}
extension InlineFragment: Fragment {}

public struct FragmentSpread {
    public let kind: Kind = .fragmentSpread
    public let loc: Location?
    public private(set) var name: Name
    public private(set) var directives: [Directive]

    init(loc: Location? = nil, name: Name, directives: [Directive] = []) {
        self.loc = loc
        self.name = name
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                break
            }
            new.directives = directives
        default:
            break
        }
        return new
    }
}

extension FragmentSpread: Equatable {
    public static func == (lhs: FragmentSpread, rhs: FragmentSpread) -> Bool {
        return lhs.name == rhs.name &&
            lhs.directives == rhs.directives
    }
}

public protocol HasTypeCondition {
    func getTypeCondition() -> NamedType?
}

extension InlineFragment: HasTypeCondition {
    public func getTypeCondition() -> NamedType? {
        return typeCondition
    }
}

extension FragmentDefinition: HasTypeCondition {
    public func getTypeCondition() -> NamedType? {
        return typeCondition
    }
}

public struct InlineFragment {
    public let kind: Kind = .inlineFragment
    public let loc: Location?
    public private(set) var typeCondition: NamedType?
    public private(set) var directives: [Directive]
    public private(set) var selectionSet: SelectionSet

    init(
        loc: Location? = nil,
        typeCondition: NamedType? = nil,
        directives: [Directive] = [],
        selectionSet: SelectionSet
    ) {
        self.loc = loc
        self.typeCondition = typeCondition
        self.directives = directives
        self.selectionSet = selectionSet
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "typeCondition":
            return typeCondition.map { .node($0) }
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        case "selectionSet":
            return .node(selectionSet)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "typeCondition":
            guard
                case let .node(node) = value,
                let typeCondition = node as? NamedType
            else {
                break
            }
            new.typeCondition = typeCondition
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                break
            }
            new.directives = directives
        case "selectionSet":
            guard
                case let .node(value) = value,
                let selectionSet = value as? SelectionSet
            else {
                break
            }
            new.selectionSet = selectionSet
        default:
            break
        }
        return new
    }
}

extension InlineFragment: Equatable {
    public static func == (lhs: InlineFragment, rhs: InlineFragment) -> Bool {
        return lhs.typeCondition == rhs.typeCondition &&
            lhs.directives == rhs.directives &&
            lhs.selectionSet == rhs.selectionSet
    }
}

public final class FragmentDefinition {
    public let kind: Kind = .fragmentDefinition
    public let loc: Location?
    public let name: Name
    public let typeCondition: NamedType
    public let directives: [Directive]
    public let selectionSet: SelectionSet

    init(
        loc: Location? = nil,
        name: Name,
        typeCondition: NamedType,
        directives: [Directive] = [],
        selectionSet: SelectionSet
    ) {
        self.loc = loc
        self.name = name
        self.typeCondition = typeCondition
        self.directives = directives
        self.selectionSet = selectionSet
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        case "typeCondition":
            return .node(typeCondition)
        case "directives":
            guard !directives.isEmpty else {
                return nil
            }
            return .array(directives)
        case "selectionSet":
            return .node(selectionSet)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                return self
            }
            return Self(
                loc: loc,
                name: name,
                typeCondition: typeCondition,
                directives: directives,
                selectionSet: selectionSet
            )
        case "typeCondition":
            guard
                case let .node(node) = value,
                let typeCondition = node as? NamedType
            else {
                return self
            }
            return Self(
                loc: loc,
                name: name,
                typeCondition: typeCondition,
                directives: directives,
                selectionSet: selectionSet
            )
        case "directives":
            guard
                case let .array(values) = value,
                let directives = values as? [Directive]
            else {
                return self
            }
            return Self(
                loc: loc,
                name: name,
                typeCondition: typeCondition,
                directives: directives,
                selectionSet: selectionSet
            )
        case "selectionSet":
            guard
                case let .node(value) = value,
                let selectionSet = value as? SelectionSet
            else {
                return self
            }
            return Self(
                loc: loc,
                name: name,
                typeCondition: typeCondition,
                directives: directives,
                selectionSet: selectionSet
            )
        default:
            return self
        }
    }
}

extension FragmentDefinition: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    public static func == (lhs: FragmentDefinition, rhs: FragmentDefinition) -> Bool {
        return lhs.name == rhs.name &&
            lhs.typeCondition == rhs.typeCondition &&
            lhs.directives == rhs.directives &&
            lhs.selectionSet == rhs.selectionSet
    }
}

public protocol Value: Node {}
extension Variable: Value {}
extension IntValue: Value {}
extension FloatValue: Value {}
extension StringValue: Value {}
extension BooleanValue: Value {}
extension NullValue: Value {}
extension EnumValue: Value {}
extension ListValue: Value {}
extension ObjectValue: Value {}

public func == (lhs: Value, rhs: Value) -> Bool {
    switch lhs {
    case let l as Variable:
        if let r = rhs as? Variable {
            return l == r
        }
    case let l as IntValue:
        if let r = rhs as? IntValue {
            return l == r
        }
    case let l as FloatValue:
        if let r = rhs as? FloatValue {
            return l == r
        }
    case let l as StringValue:
        if let r = rhs as? StringValue {
            return l == r
        }
    case let l as BooleanValue:
        if let r = rhs as? BooleanValue {
            return l == r
        }
    case let l as NullValue:
        if let r = rhs as? NullValue {
            return l == r
        }
    case let l as EnumValue:
        if let r = rhs as? EnumValue {
            return l == r
        }
    case let l as ListValue:
        if let r = rhs as? ListValue {
            return l == r
        }
    case let l as ObjectValue:
        if let r = rhs as? ObjectValue {
            return l == r
        }
    default:
        return false
    }

    return false
}

public struct IntValue {
    public let kind: Kind = .intValue
    public let loc: Location?
    public let value: String

    init(loc: Location? = nil, value: String) {
        self.loc = loc
        self.value = value
    }
}

extension IntValue: Equatable {
    public static func == (lhs: IntValue, rhs: IntValue) -> Bool {
        return lhs.value == rhs.value
    }
}

public struct FloatValue {
    public let kind: Kind = .floatValue
    public let loc: Location?
    public let value: String

    init(loc: Location? = nil, value: String) {
        self.loc = loc
        self.value = value
    }
}

extension FloatValue: Equatable {
    public static func == (lhs: FloatValue, rhs: FloatValue) -> Bool {
        return lhs.value == rhs.value
    }
}

public struct StringValue: Sendable {
    public let kind: Kind = .stringValue
    public let loc: Location?
    public let value: String
    public let block: Bool?

    init(loc: Location? = nil, value: String, block: Bool? = nil) {
        self.loc = loc
        self.value = value
        self.block = block
    }
}

extension StringValue: Equatable {
    public static func == (lhs: StringValue, rhs: StringValue) -> Bool {
        return lhs.value == rhs.value && lhs.block == rhs.block
    }
}

public struct BooleanValue {
    public let kind: Kind = .booleanValue
    public let loc: Location?
    public let value: Bool

    init(loc: Location? = nil, value: Bool) {
        self.loc = loc
        self.value = value
    }
}

extension BooleanValue: Equatable {
    public static func == (lhs: BooleanValue, rhs: BooleanValue) -> Bool {
        return lhs.value == rhs.value
    }
}

public struct NullValue {
    public let kind: Kind = .nullValue
    public let loc: Location?

    init(loc: Location? = nil) {
        self.loc = loc
    }
}

extension NullValue: Equatable {
    public static func == (_: NullValue, _: NullValue) -> Bool {
        return true
    }
}

public struct EnumValue {
    public let kind: Kind = .enumValue
    public let loc: Location?
    public let value: String

    init(loc: Location? = nil, value: String) {
        self.loc = loc
        self.value = value
    }
}

extension EnumValue: Equatable {
    public static func == (lhs: EnumValue, rhs: EnumValue) -> Bool {
        return lhs.value == rhs.value
    }
}

public struct ListValue {
    public let kind: Kind = .listValue
    public let loc: Location?
    public private(set) var values: [Value]

    init(loc: Location? = nil, values: [Value]) {
        self.loc = loc
        self.values = values
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "values":
            return .array(values)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "values":
            guard
                case let .array(values) = value,
                let values = values as? [Value]
            else {
                break
            }
            new.values = values
        default:
            break
        }
        return new
    }
}

extension ListValue: Equatable {
    public static func == (lhs: ListValue, rhs: ListValue) -> Bool {
        guard lhs.values.count == rhs.values.count else {
            return false
        }

        for (l, r) in zip(lhs.values, rhs.values) {
            guard l == r else {
                return false
            }
        }

        return true
    }
}

public struct ObjectValue {
    public let kind: Kind = .objectValue
    public let loc: Location?
    public private(set) var fields: [ObjectField]

    init(loc: Location? = nil, fields: [ObjectField]) {
        self.loc = loc
        self.fields = fields
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "fields":
            return .array(fields)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "fields":
            guard
                case let .array(values) = value,
                let fields = values as? [ObjectField]
            else {
                break
            }
            new.fields = fields
        default:
            break
        }
        return new
    }
}

extension ObjectValue: Equatable {
    public static func == (lhs: ObjectValue, rhs: ObjectValue) -> Bool {
        return lhs.fields == rhs.fields
    }
}

public struct ObjectField {
    public let kind: Kind = .objectField
    public let loc: Location?
    public private(set) var name: Name
    public private(set) var value: Value

    init(loc: Location? = nil, name: Name, value: Value) {
        self.loc = loc
        self.name = name
        self.value = value
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        case "value":
            return .node(value)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        case "value":
            guard
                case let .node(node) = value,
                let value = node as? Value
            else {
                break
            }
            new.value = value
        default:
            break
        }
        return new
    }
}

extension ObjectField: Equatable {
    public static func == (lhs: ObjectField, rhs: ObjectField) -> Bool {
        return lhs.name == rhs.name &&
            lhs.value == rhs.value
    }
}

public struct Directive: Sendable {
    public let kind: Kind = .directive
    public let loc: Location?
    public private(set) var name: Name
    public private(set) var arguments: [Argument]

    init(loc: Location? = nil, name: Name, arguments: [Argument] = []) {
        self.loc = loc
        self.name = name
        self.arguments = arguments
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        case "arguments":
            return .array(arguments)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        case "arguments":
            guard
                case let .array(nodes) = value,
                let arguments = nodes as? [Argument]
            else {
                break
            }
            new.arguments = arguments
        default:
            break
        }
        return new
    }
}

extension Directive: Equatable {
    public static func == (lhs: Directive, rhs: Directive) -> Bool {
        return lhs.name == rhs.name &&
            lhs.arguments == rhs.arguments
    }
}

public protocol Type: Node {}
extension NamedType: Type {}
extension ListType: Type {}
extension NonNullType: Type {}

public func == (lhs: Type, rhs: Type) -> Bool {
    switch lhs {
    case let l as NamedType:
        if let r = rhs as? NamedType {
            return l == r
        }
    case let l as ListType:
        if let r = rhs as? ListType {
            return l == r
        }
    case let l as NonNullType:
        if let r = rhs as? NonNullType {
            return l == r
        }
    default:
        return false
    }

    return false
}

public struct NamedType {
    public let kind: Kind = .namedType
    public let loc: Location?
    public private(set) var name: Name

    init(loc: Location? = nil, name: Name) {
        self.loc = loc
        self.name = name
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "name":
            return .node(name)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "name":
            guard
                case let .node(node) = value,
                let name = node as? Name
            else {
                break
            }
            new.name = name
        default:
            break
        }
        return new
    }
}

extension NamedType: Equatable {
    public static func == (lhs: NamedType, rhs: NamedType) -> Bool {
        return lhs.name == rhs.name
    }
}

public struct ListType {
    public let kind: Kind = .listType
    public let loc: Location?
    public private(set) var type: Type

    init(loc: Location? = nil, type: Type) {
        self.loc = loc
        self.type = type
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "type":
            return .node(type)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "type":
            guard
                case let .node(node) = value,
                let type = node as? Type
            else {
                break
            }
            new.type = type
        default:
            break
        }
        return new
    }
}

extension ListType: Equatable {
    public static func == (lhs: ListType, rhs: ListType) -> Bool {
        return lhs.type == rhs.type
    }
}

public protocol NonNullableType: Type {}
extension ListType: NonNullableType {}
extension NamedType: NonNullableType {}

public struct NonNullType {
    public let kind: Kind = .nonNullType
    public let loc: Location?
    public private(set) var type: NonNullableType

    init(loc: Location? = nil, type: NonNullableType) {
        self.loc = loc
        self.type = type
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "type":
            return .node(type)
        default:
            return nil
        }
    }

    public func set(value: NodeResult?, key: String) -> Self {
        guard let value = value else {
            return self
        }
        var new = self
        switch key {
        case "type":
            guard
                case let .node(node) = value,
                let type = node as? NonNullableType
            else {
                break
            }
            new.type = type
        default:
            break
        }
        return new
    }
}

extension NonNullType: Equatable {
    public static func == (lhs: NonNullType, rhs: NonNullType) -> Bool {
        return lhs.type == rhs.type
    }
}

// Type System Definition
// experimental non-spec addition.
public protocol TypeSystemDefinition: Definition {}
extension SchemaDefinition: TypeSystemDefinition {}
extension TypeExtensionDefinition: TypeSystemDefinition {}
extension SchemaExtensionDefinition: TypeSystemDefinition {}
extension DirectiveDefinition: TypeSystemDefinition {}
extension InterfaceExtensionDefinition: TypeSystemDefinition {}
extension ScalarExtensionDefinition: TypeSystemDefinition {}
extension UnionExtensionDefinition: TypeSystemDefinition {}
extension EnumExtensionDefinition: TypeSystemDefinition {}
extension InputObjectExtensionDefinition: TypeSystemDefinition {}

public func == (lhs: TypeSystemDefinition, rhs: TypeSystemDefinition) -> Bool {
    switch lhs {
    case let l as SchemaDefinition:
        if let r = rhs as? SchemaDefinition {
            return l == r
        }
    case let l as TypeExtensionDefinition:
        if let r = rhs as? TypeExtensionDefinition {
            return l == r
        }
    case let l as DirectiveDefinition:
        if let r = rhs as? DirectiveDefinition {
            return l == r
        }
    case let l as TypeDefinition:
        if let r = rhs as? TypeDefinition {
            return l == r
        }
    case let l as SchemaExtensionDefinition:
        if let r = rhs as? SchemaExtensionDefinition {
            return l == r
        }
    case let l as InterfaceExtensionDefinition:
        if let r = rhs as? InterfaceExtensionDefinition {
            return l == r
        }
    case let l as ScalarExtensionDefinition:
        if let r = rhs as? ScalarExtensionDefinition {
            return l == r
        }
    case let l as UnionExtensionDefinition:
        if let r = rhs as? UnionExtensionDefinition {
            return l == r
        }
    case let l as EnumExtensionDefinition:
        if let r = rhs as? EnumExtensionDefinition {
            return l == r
        }
    case let l as InputObjectExtensionDefinition:
        if let r = rhs as? InputObjectExtensionDefinition {
            return l == r
        }
    default:
        return false
    }

    return false
}

public struct SchemaDefinition {
    public let kind: Kind = .schemaDefinition
    public let loc: Location?
    public let description: StringValue?
    public let directives: [Directive]
    public let operationTypes: [OperationTypeDefinition]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        directives: [Directive],
        operationTypes: [OperationTypeDefinition]
    ) {
        self.loc = loc
        self.description = description
        self.directives = directives
        self.operationTypes = operationTypes
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "directives":
            return .array(directives)
        case "operationTypes":
            return .array(operationTypes)
        default:
            return nil
        }
    }
}

extension SchemaDefinition: Equatable {
    public static func == (lhs: SchemaDefinition, rhs: SchemaDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.directives == rhs.directives &&
            lhs.operationTypes == rhs.operationTypes
    }
}

public struct OperationTypeDefinition {
    public let kind: Kind = .operationDefinition
    public let loc: Location?
    public let operation: OperationType
    public let type: NamedType

    init(loc: Location? = nil, operation: OperationType, type: NamedType) {
        self.loc = loc
        self.operation = operation
        self.type = type
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "type":
            return .node(type)
        default:
            return nil
        }
    }
}

extension OperationTypeDefinition: Equatable {
    public static func == (lhs: OperationTypeDefinition, rhs: OperationTypeDefinition) -> Bool {
        return lhs.operation == rhs.operation &&
            lhs.type == rhs.type
    }
}

public protocol TypeDefinition: TypeSystemDefinition {
    var name: Name { get }
}

extension ScalarTypeDefinition: TypeDefinition {}
extension ObjectTypeDefinition: TypeDefinition {}
extension InterfaceTypeDefinition: TypeDefinition {}
extension UnionTypeDefinition: TypeDefinition {}
extension EnumTypeDefinition: TypeDefinition {}
extension InputObjectTypeDefinition: TypeDefinition {}

public func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool {
    switch lhs {
    case let l as ScalarTypeDefinition:
        if let r = rhs as? ScalarTypeDefinition {
            return l == r
        }
    case let l as ObjectTypeDefinition:
        if let r = rhs as? ObjectTypeDefinition {
            return l == r
        }
    case let l as InterfaceTypeDefinition:
        if let r = rhs as? InterfaceTypeDefinition {
            return l == r
        }
    case let l as UnionTypeDefinition:
        if let r = rhs as? UnionTypeDefinition {
            return l == r
        }
    case let l as EnumTypeDefinition:
        if let r = rhs as? EnumTypeDefinition {
            return l == r
        }
    case let l as InputObjectTypeDefinition:
        if let r = rhs as? InputObjectTypeDefinition {
            return l == r
        }
    default:
        return false
    }

    return false
}

public struct ScalarTypeDefinition: Sendable {
    public let kind: Kind = .scalarTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let directives: [Directive]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        directives: [Directive] = []
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "directives":
            return .array(directives)
        default:
            return nil
        }
    }
}

extension ScalarTypeDefinition: Equatable {
    public static func == (lhs: ScalarTypeDefinition, rhs: ScalarTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives
    }
}

public struct ObjectTypeDefinition {
    public let kind: Kind = .objectTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let interfaces: [NamedType]
    public let directives: [Directive]
    public let fields: [FieldDefinition]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        interfaces: [NamedType] = [],
        directives: [Directive] = [],
        fields: [FieldDefinition] = []
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.interfaces = interfaces
        self.directives = directives
        self.fields = fields
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "interfaces":
            return .array(interfaces)
        case "directives":
            return .array(directives)
        case "fields":
            return .array(fields)
        default:
            return nil
        }
    }
}

extension ObjectTypeDefinition: Equatable {
    public static func == (lhs: ObjectTypeDefinition, rhs: ObjectTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.interfaces == rhs.interfaces &&
            lhs.directives == rhs.directives &&
            lhs.fields == rhs.fields
    }
}

public struct FieldDefinition {
    public let kind: Kind = .fieldDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let arguments: [InputValueDefinition]
    public let type: Type
    public let directives: [Directive]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        arguments: [InputValueDefinition] = [],
        type: Type,
        directives: [Directive] = []
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.arguments = arguments
        self.type = type
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "arguments":
            return .array(arguments)
        case "type":
            return .node(type)
        case "directives":
            return .array(directives)
        default:
            return nil
        }
    }
}

extension FieldDefinition: Equatable {
    public static func == (lhs: FieldDefinition, rhs: FieldDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.arguments == rhs.arguments &&
            lhs.type == rhs.type &&
            lhs.directives == rhs.directives
    }
}

public struct InputValueDefinition: Sendable {
    public let kind: Kind = .inputValueDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let type: Type
    public let defaultValue: Value?
    public let directives: [Directive]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        type: Type,
        defaultValue: Value? = nil,
        directives: [Directive] = []
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.type = type
        self.defaultValue = defaultValue
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "type":
            return .node(type)
        case "defaultValue":
            return defaultValue.map { .node($0) }
        case "directives":
            return .array(directives)
        default:
            return nil
        }
    }
}

extension InputValueDefinition: Equatable {
    public static func == (lhs: InputValueDefinition, rhs: InputValueDefinition) -> Bool {
        guard lhs.name == rhs.name else {
            return false
        }

        guard lhs.type == rhs.type else {
            return false
        }

        guard lhs.directives == rhs.directives else {
            return false
        }

        if lhs.defaultValue == nil, rhs.defaultValue == nil {
            return true
        }

        guard let l = lhs.defaultValue, let r = rhs.defaultValue else {
            return false
        }

        return l == r
    }
}

public struct InterfaceTypeDefinition {
    public let kind: Kind = .interfaceTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let interfaces: [NamedType]
    public let directives: [Directive]
    public let fields: [FieldDefinition]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        interfaces: [NamedType] = [],
        directives: [Directive] = [],
        fields: [FieldDefinition]
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.interfaces = interfaces
        self.directives = directives
        self.fields = fields
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "interfaces":
            return .array(interfaces)
        case "directives":
            return .array(directives)
        case "fields":
            return .array(fields)
        default:
            return nil
        }
    }
}

extension InterfaceTypeDefinition: Equatable {
    public static func == (lhs: InterfaceTypeDefinition, rhs: InterfaceTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives &&
            lhs.fields == rhs.fields
    }
}

public struct UnionTypeDefinition {
    public let kind: Kind = .unionTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let directives: [Directive]
    public let types: [NamedType]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        directives: [Directive] = [],
        types: [NamedType]
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.directives = directives
        self.types = types
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "directives":
            return .array(directives)
        case "types":
            return .array(types)
        default:
            return nil
        }
    }
}

extension UnionTypeDefinition: Equatable {
    public static func == (lhs: UnionTypeDefinition, rhs: UnionTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives &&
            lhs.types == rhs.types
    }
}

public struct EnumTypeDefinition: Sendable {
    public let kind: Kind = .enumTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let directives: [Directive]
    public let values: [EnumValueDefinition]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        directives: [Directive] = [],
        values: [EnumValueDefinition]
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.directives = directives
        self.values = values
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "directives":
            return .array(directives)
        case "values":
            return .array(values)
        default:
            return nil
        }
    }
}

extension EnumTypeDefinition: Equatable {
    public static func == (lhs: EnumTypeDefinition, rhs: EnumTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives &&
            lhs.values == rhs.values
    }
}

public struct EnumValueDefinition: Sendable {
    public let kind: Kind = .enumValueDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let directives: [Directive]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        directives: [Directive] = []
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "directives":
            return .array(directives)
        default:
            return nil
        }
    }
}

extension EnumValueDefinition: Equatable {
    public static func == (lhs: EnumValueDefinition, rhs: EnumValueDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives
    }
}

public struct InputObjectTypeDefinition {
    public let kind: Kind = .inputObjectTypeDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let directives: [Directive]
    public let fields: [InputValueDefinition]

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        directives: [Directive] = [],
        fields: [InputValueDefinition]
    ) {
        self.loc = loc
        self.description = description
        self.name = name
        self.directives = directives
        self.fields = fields
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "directives":
            return .array(directives)
        case "fields":
            return .array(fields)
        default:
            return nil
        }
    }
}

extension InputObjectTypeDefinition: Equatable {
    public static func == (lhs: InputObjectTypeDefinition, rhs: InputObjectTypeDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.directives == rhs.directives &&
            lhs.fields == rhs.fields
    }
}

protocol TypeExtension: TypeSystemDefinition {
    var name: Name { get }
}

extension ScalarExtensionDefinition: TypeExtension {}
extension TypeExtensionDefinition: TypeExtension {}
extension InterfaceExtensionDefinition: TypeExtension {}
extension UnionExtensionDefinition: TypeExtension {}
extension EnumExtensionDefinition: TypeExtension {}
extension InputObjectExtensionDefinition: TypeExtension {}

public struct TypeExtensionDefinition {
    public let kind: Kind = .typeExtensionDefinition
    public let loc: Location?
    public let definition: ObjectTypeDefinition

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: ObjectTypeDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension TypeExtensionDefinition: Equatable {
    public static func == (lhs: TypeExtensionDefinition, rhs: TypeExtensionDefinition) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct SchemaExtensionDefinition {
    public let kind: Kind = .schemaExtensionDefinition
    public let loc: Location?
    public let definition: SchemaDefinition

    init(loc: Location? = nil, definition: SchemaDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension SchemaExtensionDefinition: Equatable {
    public static func == (lhs: SchemaExtensionDefinition, rhs: SchemaExtensionDefinition) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct InterfaceExtensionDefinition {
    public let kind: Kind = .interfaceExtensionDefinition
    public let loc: Location?
    public let definition: InterfaceTypeDefinition

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: InterfaceTypeDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension InterfaceExtensionDefinition: Equatable {
    public static func == (
        lhs: InterfaceExtensionDefinition,
        rhs: InterfaceExtensionDefinition
    ) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct ScalarExtensionDefinition: Sendable {
    public let kind: Kind = .scalarExtensionDefinition
    public let loc: Location?
    public let definition: ScalarTypeDefinition
    public let directives: [Directive]

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: ScalarTypeDefinition, directives: [Directive] = []) {
        self.loc = loc
        self.definition = definition
        self.directives = directives
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension ScalarExtensionDefinition: Equatable {
    public static func == (lhs: ScalarExtensionDefinition, rhs: ScalarExtensionDefinition) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct UnionExtensionDefinition {
    public let kind: Kind = .unionExtensionDefinition
    public let loc: Location?
    public let definition: UnionTypeDefinition

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: UnionTypeDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension UnionExtensionDefinition: Equatable {
    public static func == (lhs: UnionExtensionDefinition, rhs: UnionExtensionDefinition) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct EnumExtensionDefinition: Sendable {
    public let kind: Kind = .enumExtensionDefinition
    public let loc: Location?
    public let definition: EnumTypeDefinition

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: EnumTypeDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension EnumExtensionDefinition: Equatable {
    public static func == (lhs: EnumExtensionDefinition, rhs: EnumExtensionDefinition) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct InputObjectExtensionDefinition {
    public let kind: Kind = .inputObjectExtensionDefinition
    public let loc: Location?
    public let definition: InputObjectTypeDefinition

    var name: Name {
        return definition.name
    }

    init(loc: Location? = nil, definition: InputObjectTypeDefinition) {
        self.loc = loc
        self.definition = definition
    }

    public func get(key: String) -> NodeResult? {
        definition.get(key: key)
    }
}

extension InputObjectExtensionDefinition: Equatable {
    public static func == (
        lhs: InputObjectExtensionDefinition,
        rhs: InputObjectExtensionDefinition
    ) -> Bool {
        return lhs.definition == rhs.definition
    }
}

public struct DirectiveDefinition {
    public let kind: Kind = .directiveDefinition
    public let loc: Location?
    public let description: StringValue?
    public let name: Name
    public let arguments: [InputValueDefinition]
    public let locations: [Name]
    public let repeatable: Bool

    init(
        loc: Location? = nil,
        description: StringValue? = nil,
        name: Name,
        arguments: [InputValueDefinition] = [],
        locations: [Name],
        repeatable: Bool = false
    ) {
        self.loc = loc
        self.name = name
        self.description = description
        self.arguments = arguments
        self.locations = locations
        self.repeatable = repeatable
    }

    public func get(key: String) -> NodeResult? {
        switch key {
        case "description":
            return description.map { .node($0) }
        case "name":
            return .node(name)
        case "arguments":
            return .array(arguments)
        case "locations":
            return .array(locations)
        default:
            return nil
        }
    }
}

extension DirectiveDefinition: Equatable {
    public static func == (lhs: DirectiveDefinition, rhs: DirectiveDefinition) -> Bool {
        return lhs.description == rhs.description &&
            lhs.name == rhs.name &&
            lhs.arguments == rhs.arguments &&
            lhs.locations == rhs.locations
    }
}
